package nio.reactor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @Desc:   一个Reactor反应器版本的EchoServer实践案例
 * @author: cww
 * @DateTime: 2020-02-08 15:49
 */

public class EchoServerReactor implements Runnable {

    Selector selector;
    ServerSocketChannel serverSocketChannel;

    /**
     * Reactor 初始化
     * @throws Exception
     */
    EchoServerReactor() throws Exception {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
        serverSocketChannel.socket().bind(address);
        serverSocketChannel.configureBlocking(false);
        // 注册 serverSocket 的 accept 事件
        SelectionKey sk = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 将新连接处理器作为附件，绑定到 sk 选择键
        sk.attach(new AcceptorHandler());
    }

    /**
     * 轮询和分发事件
     */
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                selector.select();
                Set<SelectionKey> selected = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selected.iterator();
                while (iterator.hasNext()) {
                    // 反应器负责dispatch收到的事件
                    SelectionKey key = iterator.next();
                    dispatch(key);
                }
                selected.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 反应器的分发方法
     * @param key
     */
    void dispatch(SelectionKey key) {
        Runnable handler = (Runnable) key.attachment();
        // 调用之前绑定到选择键的处理器对象
        if (handler != null) {
            handler.run();
        }
    }

    /**
     * 新连接处理器
     */
    class AcceptorHandler implements Runnable {

        @Override
        public void run() {
            try {
                // 接受新连接
                SocketChannel channel = serverSocketChannel.accept();
                // 为新连接创建一个输入输出的 handler 处理器
                if (channel != null) {
                    // 回显处理器
                    new EchoHandler(selector, channel);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new Thread(new EchoServerReactor()).start();
    }
}
